﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Konves.Scripture
{
    /// <summary>
    /// Represents a range of scripture verses.
    /// </summary>
    public class Range
    {
        /// <summary>
        /// Creates a RangeInfo instance from a RawRange.
        /// </summary>
        /// <param name="range">The range.</param>
        /// <param name="mode">The mode.</param>
        /// <returns></returns>
        internal static Range Create(Data.RawRange range, RepositoryMode mode)
        {
            Range result = new Range();

            result.Start = Verse.Create(range.FirstBook, range.FirstChapter, range.FirstVerse, range.FirstVerseSuffix, VersePosition.First, mode);
            result.End = Verse.Create(range.SecondBook, range.SecondChapter, range.SecondVerse, range.SecondVerseSuffix, VersePosition.Last, mode);

            if (mode != RepositoryMode.Optimistic && (result.Start == null || result.End == null))
                result = null;

            return result;
        }

        /// <summary>
        /// Gets a the verse at the start of this range.
        /// </summary>
        /// <value>
        /// The starting verse.
        /// </value>
        public Verse Start { get; internal set; }

        /// <summary>
        /// Gets the verse at the end of this range.
        /// </summary>
        /// <value>
        /// The ending verse.
        /// </value>
        public Verse End { get; internal set; }

        /// <summary>
        /// Determines whether this <see cref="Range"/> contains the specified <see cref="Verse"/>.
        /// </summary>
        /// <param name="verse">The <see cref="Verse"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Range"/> contains the specified <see cref="Verse"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="verse"/> is null.</exception>
        public bool Contains(Verse verse)
        {
            if (verse == null)
                throw new ArgumentNullException("verse", "verse is null");

            return this.Start.Index <= verse.Index && this.End.BookId >= verse.Index;
        }

        /// <summary>
        /// Determines whether this <see cref="Range"/> contains all of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>.
        /// </summary>
        /// <param name="range">The <see cref="Range"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Range"/> contains all of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="range"/> is null.</exception>
        public bool Contains(Range range)
        {
            if (range == null)
                throw new ArgumentNullException("range", "range is null");

            return this.Start.Index <= range.Start.Index && this.End.BookId >= range.End.Index;
        }

        /// <summary>
        /// Determines whether this <see cref="Range"/> contains all of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Reference"/>.
        /// </summary>
        /// <param name="reference">The <see cref="Reference"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Range"/> contains all of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Reference"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="reference"/> is null.</exception>
        public bool Contains(Reference reference)
        {
            if (reference == null)
                throw new ArgumentNullException("reference", "reference is null");

            return reference.All(range => this.Contains(range));
        }

        /// <summary>
        /// Determines whether this <see cref="Range"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>.
        /// </summary>
        /// <param name="range">The <see cref="Range"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Range"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="range"/> is null.</exception>
        public bool Intersects(Range range)
        {
            if (range == null)
                throw new ArgumentNullException("range", "range is null");

            return this.Contains(range.Start) || this.Contains(range.End);
        }

        /// <summary>
        /// Determines whether this <see cref="Range"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Reference"/>.
        /// </summary>
        /// <param name="reference">The <see cref="Reference"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Range"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Reference"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="reference"/> is null.</exception>
        public bool Intersects(Reference reference)
        {
            if (reference == null)
                throw new ArgumentNullException("reference", "reference is null");

            return reference.Any(range => this.Intersects(range));
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            // TODO: add funtionality to create short strings: "Genesis 1-6" or "Matthew-John"

            if (Start != null && End != null)
            {
                if (Start.BookId == End.BookId && Start.ChapterNumber == End.ChapterNumber && Start.VerseNumber == End.VerseNumber)
                {
                    return Start.ToString();
                }
                else if (Start.BookId == End.BookId && Start.ChapterNumber == End.ChapterNumber)
                {
                    return Start.BookName + " " + Start.ChapterNumber + ":" + Start.VerseNumber + Start.Suffix + "-" + End.VerseNumber + End.Suffix;
                }
                else if (Start.BookId == End.BookId )
                {
                    return Start.BookName + " " + Start.ChapterNumber + ":" + Start.VerseNumber + Start.Suffix + "-" + End.ChapterNumber + ":" + End.VerseNumber + End.Suffix;
                }
                else
                    return Start.ToString() + " - " + End.ToString();
            }
            else
                return base.ToString();
        }
        
        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            // TODO: move to an IEqualityComparer
            return this.GetHashCode() == obj.GetHashCode();
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            // TODO: move to an IEqualityComparer
            return
                Start.GetHashCode()
                ^ (End.GetHashCode() >> 1);
        }
    }
}
